<!DOCTYPE html>
<meta charset=utf8>
<title>Test getComputedStyle on a CSS animation in a content visibility subtree using content-visibility: auto</title>
<link rel="help" href="https://drafts.csswg.org/css-contain-2/">
<script src="/web-animations/testcommon.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#container {
  content-visibility: auto;
}
@keyframes fade {
  from { opacity: 1; }
  to { opacity: 0;  }
}
#target {
  background: green;
  height: 100px;
  width: 100px;
}
.animate {
  animation: fade 1s linear 2 alternate;
}
.transition {
  transition: opacity 1s linear;
}
</style>
<body>
  <div id="spacer"></div>
  <div id="container"></div>
</body>
<script>
"use strict";

function reset() {
  const container = document.getElementById('container');
  const target = document.getElementById('target');
  container.style = '';
  container.removeChild(target);
}

function createAnimatingElement(test, name) {
  const container = document.getElementById('container');
  const target = document.createElement('div');
  container.appendChild(target);
  target.id = 'target';
  target.className = name;
  test.add_cleanup(() => {
    reset();
  });
  return target;
}

promise_test(async t => {
  const container = document.getElementById('container');
  const target = createAnimatingElement(t, 'animate');
  let animationIterationEvent = false;
  const animation = target.getAnimations()[0];
  await animation.ready;
  await waitForAnimationFrames(1);
  document.getElementById("spacer").style.height = "300vh";
  await waitForAnimationFrames(1);
  target.addEventListener('animationiteration', () => {
    animationIterationEvent = true;
  });
  animation.currentTime = 1500;
  assert_approx_equals(
      parseFloat(getComputedStyle(target).opacity), 0.5, 1e-6,
      'Computed style is updated even when the animation is running in a ' +
      'content visibility subtree');
  await waitForAnimationFrames(2);
  assert_false(animationIterationEvent,
               'Animation events do not fire while the animation is ' +
               'running in a content visibility subtree');
  document.getElementById("spacer").style.height = "0vh";
  await waitForAnimationFrames(2);
  assert_true(animationIterationEvent,
              'The animationiteration event fires once the animation is ' +
              'no longer content visibility');
}, 'Animation events do not fire for a CSS animation running in a content ' +
   'visibility subtree');

promise_test(async t => {
  const container = document.getElementById('container');
  const target = createAnimatingElement(t, 'animate');
  const animation = target.getAnimations()[0];
  await animation.ready;
  let finishedWhileDisplayLocked = false;
  animation.finished.then(() => {
    finishedWhileDisplayLocked =
        getComputedStyle(target).height == '0px';
  });
  await waitForAnimationFrames(1);
  document.getElementById("spacer").style.height = "300vh";
  // Advance to just shy of the effect end.
  animation.currentTime = 1999;
  assert_approx_equals(
      parseFloat(getComputedStyle(target).opacity), 0.999, 1e-6,
                'Computed style is updated even when the animation is ' +
                'running in a content visibility subtree');
  // Advancing frames should not resolve the finished promise.
  await waitForAnimationFrames(3);
  document.getElementById("spacer").style.height = "0vh";
  // Now we can resolve the finished promise.
  await animation.finished;
  assert_equals(finishedWhileDisplayLocked, false);
}, 'The finished promise does not resolve due to the normal passage of time  ' +
   'for a CSS animation in a content visibility subtree');

promise_test(async t => {
  const container = document.getElementById('container');
  await waitForAnimationFrames(1);
  const target = createAnimatingElement(t, 'transition');
  await waitForAnimationFrames(1);
  target.style.opacity = 0;
  const animation = target.getAnimations()[0];
  await animation.ready;
  let finishedWhileDisplayLocked = false;
  animation.finished.then(() => {
    finishedWhileDisplayLocked =
        getComputedStyle(target).height == '0px';
  });
  await waitForAnimationFrames(1);
  document.getElementById("spacer").style.height = "300vh";
  // Advance to just shy of the effect end.
  animation.currentTime = 999;
  assert_approx_equals(
      parseFloat(getComputedStyle(target).opacity), 0.001, 1e-6,
                'Computed style is updated even when the animation is ' +
                'running in a content visibility subtree');
  // Advancing frames should not resolve the finished promise.
  await waitForAnimationFrames(3);
  document.getElementById("spacer").style.height = "0vh";
  // Now we can resolve the finished promise.
  await animation.finished;
  assert_equals(finishedWhileDisplayLocked, false);
}, 'The finished promise does not resolve due to the normal passage of time  ' +
   'for a CSS transition in a content visibility subtree');

promise_test(async t => {
  const container = document.getElementById('container');
  const target = createAnimatingElement(t, 'animate');
  const animation = target.getAnimations()[0];
  target.className = '';
  document.getElementById("spacer").style.height = "300vh";
  assert_equals(target.getAnimations().length, 0);

  // Though originally a CSS animation, it is no longer associated with
  // CSS rules and no longer has an owning element. It now behaves like a
  // programmatic web animation. Animation playback events (but not CSS
  // animation events) should be dispatched and promises resolved despite
  // being in a content visibility subtree.

  let cssAnimationEndEvent = false;
  target.addEventListener('animationend', () => {
    cssAnimationEndEvent = true;
  });

  let animationFinishEvent = false;
  animation.addEventListener('finish', () => {
    animationFinishEvent = true;
  });

  let animationFinished = false;
  animation.finished.then(() => {
    animationFinished = true;
  });

  animation.play();
  assert_equals(target.getAnimations().length, 1);

  animation.currentTime = 1999;
  await animation.ready;
  await waitForAnimationFrames(2);

  assert_true(animationFinishEvent,
              'Animation event not blocked on content visibility subtree if ' +
              'no owning element');
  assert_true(animationFinished,
              'Finished promise not blocked on content visibility subtree if ' +
              'no owning element');
  assert_false(cssAnimationEndEvent,
              'CSS animation events should not be dispatched if there is no ' +
              'owning element');
}, 'Events and promises are handled normally for animations without an ' +
   'owning element');

promise_test(async t => {
  // The animation is hidden when it is created.
  document.getElementById("spacer").style.height = "300vh";
  const container = document.getElementById('container');
  const target = createAnimatingElement(t, 'animate');
  const animation = target.getAnimations()[0];
  await waitForAnimationFrames(2);
  // Make this animation no longer associated with its owning element.
  target.className = '';
  assert_equals(target.getAnimations().length, 0);

  // Though originally a CSS animation, it is no longer associated with
  // CSS rules and no longer has an owning element. It now behaves like a
  // programmatic web animation. Animation playback events (but not CSS
  // animation events) should be dispatched and promises resolved despite
  // being in a content visibility subtree.

  let cssAnimationEndEvent = false;
  target.addEventListener('animationend', () => {
    cssAnimationEndEvent = true;
  });

  let animationFinishEvent = false;
  animation.addEventListener('finish', () => {
    animationFinishEvent = true;
  });

  let animationFinished = false;
  animation.finished.then(() => {
    animationFinished = true;
  });

  animation.play();
  assert_equals(target.getAnimations().length, 1);

  animation.currentTime = 1999;
  await animation.ready;
  await waitForAnimationFrames(2);

  assert_true(animationFinishEvent,
              'Animation event not blocked on content visibility subtree if ' +
              'no owning element');
  assert_true(animationFinished,
              'Finished promise not blocked on content visibility subtree if ' +
              'no owning element');
  assert_false(cssAnimationEndEvent,
              'CSS animation events should not be dispatched if there is no ' +
              'owning element');
}, 'CSS animations without an owning element should handle events and promises ' +
              'normally, even c-v value does change');

</script>
